package com.liuhuanss.voiceprint.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.nio.charset.StandardCharsets;

/**
 * 音频处理工具类
 * 
 * @author liuhuanss
 * @version 1.0
 */
@Component
@Slf4j
public class AudioUtil {
    
    // 支持的音频格式
    private static final List<String> SUPPORTED_FORMATS = Arrays.asList("wav", "mp3", "pcm");
    
    // 最大文件大小 - 根据讯飞API要求，base64编码后不超过4M
    // 考虑到base64编码会增加约33%的大小，原始文件应限制在3MB以内
    private static final long MAX_FILE_SIZE = 3 * 1024 * 1024; // 3MB

    /**
     * 验证音频文件格式
     * 
     * @param fileName 文件名
     * @return 是否支持
     */
    public boolean isSupportedFormat(String fileName) {
        if (fileName == null || fileName.isEmpty()) {
            return false;
        }
        
        String extension = getFileExtension(fileName).toLowerCase();
        boolean supported = SUPPORTED_FORMATS.contains(extension);
        
        log.debug("文件 {} 格式检查结果: {}", fileName, supported);
        return supported;
    }

    /**
     * 验证音频文件大小
     * 
     * @param fileSize 文件大小
     * @return 是否符合要求
     */
    public boolean isValidFileSize(long fileSize) {
        boolean valid = fileSize > 0 && fileSize <= MAX_FILE_SIZE;
        log.debug("文件大小 {} 字节，验证结果: {}", fileSize, valid);
        return valid;
    }

    /**
     * 将MultipartFile转换为Base64字符串
     * 
     * @param file 上传的文件
     * @return Base64编码的音频数据
     * @throws IOException IO异常
     */
    public String multipartFileToBase64(MultipartFile file) throws IOException {
        return convertToBase64(file);
    }
    
    /**
     * 将MultipartFile转换为Base64字符串
     * 
     * @param file 上传的文件
     * @return Base64编码的音频数据
     * @throws IOException IO异常
     */
    public String convertToBase64(MultipartFile file) throws IOException {
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("音频文件不能为空");
        }
        
        // 验证文件格式
        if (!isSupportedFormat(file.getOriginalFilename())) {
            throw new IllegalArgumentException("不支持的音频格式，支持的格式: " + SUPPORTED_FORMATS);
        }
        
        // 验证文件大小
        if (!isValidFileSize(file.getSize())) {
            throw new IllegalArgumentException("文件大小超出限制，最大支持 " + (MAX_FILE_SIZE / 1024 / 1024) + "MB");
        }
        
        try {
            byte[] audioBytes = file.getBytes();
            String ext = getFileExtension(file.getOriginalFilename()).toLowerCase();
            String base64Audio;
            if ("wav".equals(ext)) {
                try {
                    byte[] pcmBytes = extractPcmFromWav(audioBytes);
                    base64Audio = Base64.getEncoder().encodeToString(pcmBytes);
                    log.debug("WAV已提取PCM数据用于上送，大小: {} 字节", pcmBytes.length);
                } catch (Exception ex) {
                    log.warn("从WAV中提取PCM失败，将回退为原始WAV字节上送，可能导致\"音频数据非法\": {}", ex.getMessage());
                    base64Audio = Base64.getEncoder().encodeToString(audioBytes);
                }
            } else if ("mp3".equals(ext)) {
                // MP3文件直接使用原始字节，讯飞API会处理MP3解码
                base64Audio = Base64.getEncoder().encodeToString(audioBytes);
                log.debug("MP3文件直接编码为Base64，大小: {} 字节", audioBytes.length);
            } else {
                base64Audio = Base64.getEncoder().encodeToString(audioBytes);
            }
            
            log.debug("音频文件 {} 转换为Base64成功，原始大小: {} 字节", 
                    file.getOriginalFilename(), audioBytes.length);
            
            return base64Audio;
        } catch (IOException e) {
            log.error("转换音频文件为Base64失败", e);
            throw new IOException("音频文件处理失败: " + e.getMessage(), e);
        }
    }

    /**
     * 将Base64字符串转换为字节数组
     * 
     * @param base64Audio Base64编码的音频数据
     * @return 音频字节数组
     */
    public byte[] convertFromBase64(String base64Audio) {
        if (base64Audio == null || base64Audio.isEmpty()) {
            throw new IllegalArgumentException("Base64音频数据不能为空");
        }
        
        try {
            byte[] audioBytes = Base64.getDecoder().decode(base64Audio);
            log.debug("Base64音频数据解码成功，大小: {} 字节", audioBytes.length);
            return audioBytes;
        } catch (IllegalArgumentException e) {
            log.error("Base64音频数据解码失败", e);
            throw new IllegalArgumentException("无效的Base64音频数据: " + e.getMessage(), e);
        }
    }

    /**
     * 从文件路径读取音频文件并转换为Base64
     * 
     * @param filePath 文件路径
     * @return Base64编码的音频数据
     * @throws IOException IO异常
     */
    public String readFileToBase64(String filePath) throws IOException {
        if (filePath == null || filePath.isEmpty()) {
            throw new IllegalArgumentException("文件路径不能为空");
        }
        
        File file = new File(filePath);
        if (!file.exists()) {
            throw new FileNotFoundException("音频文件不存在: " + filePath);
        }
        
        if (!file.isFile()) {
            throw new IllegalArgumentException("指定路径不是文件: " + filePath);
        }
        
        // 验证文件格式
        if (!isSupportedFormat(file.getName())) {
            throw new IllegalArgumentException("不支持的音频格式，支持的格式: " + SUPPORTED_FORMATS);
        }
        
        // 验证文件大小
        if (!isValidFileSize(file.length())) {
            throw new IllegalArgumentException("文件大小超出限制，最大支持 " + (MAX_FILE_SIZE / 1024 / 1024) + "MB");
        }
        
        try (FileInputStream fis = new FileInputStream(file)) {
            byte[] audioBytes = inputStreamToByteArray(fis);
            String base64Audio = Base64.getEncoder().encodeToString(audioBytes);
            
            log.debug("从文件 {} 读取音频并转换为Base64成功，大小: {} 字节", 
                    filePath, audioBytes.length);
            
            return base64Audio;
        } catch (IOException e) {
            log.error("读取音频文件失败: {}", filePath, e);
            throw new IOException("读取音频文件失败: " + e.getMessage(), e);
        }
    }

    /**
     * 保存Base64音频数据到文件
     * 
     * @param base64Audio Base64编码的音频数据
     * @param filePath 保存路径
     * @throws IOException IO异常
     */
    public void saveBase64ToFile(String base64Audio, String filePath) throws IOException {
        if (base64Audio == null || base64Audio.isEmpty()) {
            throw new IllegalArgumentException("Base64音频数据不能为空");
        }
        
        if (filePath == null || filePath.isEmpty()) {
            throw new IllegalArgumentException("文件路径不能为空");
        }
        
        try {
            byte[] audioBytes = Base64.getDecoder().decode(base64Audio);
            
            // 确保目录存在
            File file = new File(filePath);
            File parentDir = file.getParentFile();
            if (parentDir != null && !parentDir.exists()) {
                parentDir.mkdirs();
            }
            
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(audioBytes);
                fos.flush();
            }
            log.debug("保存Base64音频数据到文件成功: {}", filePath);
        } catch (IOException e) {
            log.error("保存Base64音频数据到文件失败: {}", filePath, e);
            throw new IOException("保存音频失败: " + e.getMessage(), e);
        }
    }

    /**
     * 获取文件扩展名
     * 
     * @param fileName 文件名
     * @return 扩展名
     */
    private String getFileExtension(String fileName) {
        int lastDotIndex = fileName.lastIndexOf('.');
        if (lastDotIndex == -1 || lastDotIndex == fileName.length() - 1) {
            return "";
        }
        return fileName.substring(lastDotIndex + 1);
    }

    /**
     * 将输入流转换为字节数组
     * 
     * @param is 输入流
     * @return 字节数组
     * @throws IOException IO异常
     */
    private byte[] inputStreamToByteArray(InputStream is) throws IOException {
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        byte[] data = new byte[4096];
        int nRead;
        while ((nRead = is.read(data, 0, data.length)) != -1) {
            buffer.write(data, 0, nRead);
        }
        return buffer.toByteArray();
    }

    /**
     * 获取支持的音频格式列表
     * 
     * @return 支持的格式列表
     */
    public List<String> getSupportedFormats() {
        return SUPPORTED_FORMATS;
    }

    /**
     * 获取最大文件大小
     * 
     * @return 最大文件大小（字节）
     */
    public long getMaxFileSize() {
        return MAX_FILE_SIZE;
    }

    /**
     * 去掉浏览器传来的 data URL 前缀，例如：data:audio/wav;base64, 只保留纯Base64数据
     * @param audioBase64 可能带有 data URL 前缀的Base64
     * @return 纯Base64字符串
     */
    public String stripDataUrlPrefix(String audioBase64) {
        if (audioBase64 == null) {
            return null;
        }
        String stripped = audioBase64.replaceFirst("^data:audio/[^;]+;base64,", "");
        if (!stripped.equals(audioBase64)) {
            log.debug("移除了audio Base64的data URL前缀");
        }
        return stripped.trim();
    }

    /**
     * 根据可能存在的 data URL 前缀判断讯飞 payload.resource.encoding
     * - mp3/mpeg => lame
     * - wav/x-wav/pcm => raw
     * 其余未知场景默认按 mp3 处理（lame）
     */
    public String detectResourceEncodingByDataUrl(String audioBase64MaybeWithPrefix) {
        if (audioBase64MaybeWithPrefix == null) {
            return "lame";
        }
        String lower = audioBase64MaybeWithPrefix.toLowerCase();
        if (lower.startsWith("data:audio/")) {
            if (lower.startsWith("data:audio/mp3") || lower.startsWith("data:audio/mpeg")) {
                log.debug("根据Data URL前缀检测到mp3/mpeg，使用资源编码: lame");
                return "lame";
            }
            if (lower.startsWith("data:audio/wav") || lower.startsWith("data:audio/x-wav") || lower.startsWith("data:audio/pcm")) {
                log.debug("根据Data URL前缀检测到wav/pcm，使用资源编码: raw");
                return "raw";
            }
        }
        return "lame";
    }

    /**
     * 根据文件名（扩展名）判断讯飞 payload.resource.encoding
     * - mp3 => lame
     * - wav/pcm => raw
     * 其余未知场景默认按 mp3 处理（lame）
     */
    public String detectResourceEncodingByFilename(String filename) {
        if (filename == null || filename.isEmpty()) {
            return "lame";
        }
        
        String ext = getFileExtension(filename).toLowerCase();
        switch (ext) {
            case "mp3":
                return "lame";
            case "wav":
            case "pcm":
                return "raw";
            default:
                return "lame";
        }
    }

    /**
     * 当资源编码为 raw 时，尝试将WAV Base64转换为纯PCM Base64；若已是PCM或其他格式则保持不变。
     */
    public String normalizeBase64ForEncoding(String audioBase64, String resourceEncoding) {
        if (audioBase64 == null || resourceEncoding == null) {
            return audioBase64;
        }
        if (!"raw".equalsIgnoreCase(resourceEncoding)) {
            return audioBase64;
        }
        try {
            byte[] bytes = Base64.getDecoder().decode(audioBase64);
            if (isWav(bytes)) {
                try {
                    byte[] pcm = extractPcmFromWav(bytes);
                    log.debug("检测到WAV容器，已提取PCM字节，长度:{}", pcm.length);
                    return Base64.getEncoder().encodeToString(pcm);
                } catch (Exception ex) {
                    log.warn("WAV解析失败，继续使用原始Base64（可能导致\"音频数据非法\"）: {}", ex.getMessage());
                    return audioBase64;
                }
            }
        } catch (IllegalArgumentException e) {
            log.warn("Base64解码失败，无法规范化为PCM: {}", e.getMessage());
        }
        return audioBase64;
    }

    private boolean isWav(byte[] bytes) {
        if (bytes == null || bytes.length < 12) return false;
        // 检查 RIFF....WAVE 标记
        return bytes[0] == 'R' && bytes[1] == 'I' && bytes[2] == 'F' && bytes[3] == 'F'
                && bytes[8] == 'W' && bytes[9] == 'A' && bytes[10] == 'V' && bytes[11] == 'E';
    }

    /**
     * 从WAV字节数组中提取PCM数据（data chunk），不做重采样或格式转换。
     */
    private byte[] extractPcmFromWav(byte[] wavBytes) throws IOException {
        if (!isWav(wavBytes)) {
            throw new IOException("非WAV格式");
        }
        int pos = 12; // 跳过 RIFF header
        while (pos + 8 <= wavBytes.length) {
            String chunkId = new String(wavBytes, pos, 4, StandardCharsets.US_ASCII);
            int chunkSize = readLEInt(wavBytes, pos + 4);
            int dataStart = pos + 8;
            if ("fmt ".equals(chunkId)) {
                // 可选：从fmt读取通道数/采样率/位深做校验
            } else if ("data".equals(chunkId)) {
                int end = Math.min(dataStart + Math.max(0, chunkSize), wavBytes.length);
                if (end <= dataStart) {
                    throw new IOException("WAV data chunk 大小无效");
                }
                return Arrays.copyOfRange(wavBytes, dataStart, end);
            }
            // chunkSize 需按偶数对齐
            pos = dataStart + ((chunkSize + 1) & ~1);
        }
        throw new IOException("未找到WAV数据块(data)");
    }

    private int readLEInt(byte[] data, int offset) {
        if (offset + 4 > data.length) return 0;
        return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8) | ((data[offset + 2] & 0xFF) << 16) | ((data[offset + 3] & 0xFF) << 24);
    }

    private int readLEShort(byte[] data, int offset) {
        if (offset + 2 > data.length) return 0;
        return (data[offset] & 0xFF) | ((data[offset + 1] & 0xFF) << 8);
    }

    /**
     * 从Base64（可带data URL前缀）检测WAV的采样率/通道/位深，若不是WAV返回null。
     */
    public int[] detectWavMetaFromBase64(String audioBase64MaybeWithPrefix) {
        if (audioBase64MaybeWithPrefix == null) return null;
        String pure = stripDataUrlPrefix(audioBase64MaybeWithPrefix);
        try {
            byte[] bytes = Base64.getDecoder().decode(pure);
            if (!isWav(bytes)) return null;
            // 解析fmt chunk（在RIFF后面的chunks里）
            int pos = 12;
            while (pos + 8 <= bytes.length) {
                String chunkId = new String(bytes, pos, 4, StandardCharsets.US_ASCII);
                int chunkSize = readLEInt(bytes, pos + 4);
                int dataStart = pos + 8;
                if ("fmt ".equals(chunkId)) {
                    int audioFormat = readLEShort(bytes, dataStart);
                    int channels = readLEShort(bytes, dataStart + 2);
                    int sampleRate = readLEInt(bytes, dataStart + 4);
                    int bitsPerSample = 16; // 默认
                    // fmt chunk至少16字节，位深在偏移14
                    if (chunkSize >= 16 && dataStart + 14 + 2 <= bytes.length) {
                        bitsPerSample = readLEShort(bytes, dataStart + 14);
                    }
                    log.debug("WAV元信息: format={}, channels={}, sampleRate={}, bitsPerSample={}", audioFormat, channels, sampleRate, bitsPerSample);
                    return new int[]{sampleRate, channels, bitsPerSample};
                }
                pos = dataStart + ((chunkSize + 1) & ~1);
            }
        } catch (IllegalArgumentException e) {
            log.warn("Base64解码失败，无法检测WAV元信息: {}", e.getMessage());
        }
        return null;
    }

    /**
     * 从MultipartFile检测WAV的采样率/通道/位深，若不是WAV返回null。
     */
    public int[] detectWavMetaFromMultipartFile(MultipartFile file) throws IOException {
        if (file == null || file.isEmpty()) return null;
        byte[] bytes = file.getBytes();
        if (!isWav(bytes)) return null;
        int pos = 12;
        while (pos + 8 <= bytes.length) {
            String chunkId = new String(bytes, pos, 4, StandardCharsets.US_ASCII);
            int chunkSize = readLEInt(bytes, pos + 4);
            int dataStart = pos + 8;
            if ("fmt ".equals(chunkId)) {
                int audioFormat = readLEShort(bytes, dataStart);
                int channels = readLEShort(bytes, dataStart + 2);
                int sampleRate = readLEInt(bytes, dataStart + 4);
                int bitsPerSample = 16;
                if (chunkSize >= 16 && dataStart + 14 + 2 <= bytes.length) {
                    bitsPerSample = readLEShort(bytes, dataStart + 14);
                }
                log.debug("WAV元信息(文件): format={}, channels={}, sampleRate={}, bitsPerSample={}", audioFormat, channels, sampleRate, bitsPerSample);
                return new int[]{sampleRate, channels, bitsPerSample};
            }
            pos = dataStart + ((chunkSize + 1) & ~1);
        }
        return null;
    }
    // 将原始PCM（任意采样率/通道/位深）转为16kHz/单声道/16bit，并返回Base64
    public String ensurePcm16kMono16bitBase64(String pcmBase64, int sampleRate, int channels, int bitDepth) {
        try {
            if (pcmBase64 == null) return null;
            if (sampleRate == 16000 && channels == 1 && bitDepth == 16) {
                return pcmBase64;
            }
            byte[] src = java.util.Base64.getDecoder().decode(pcmBase64);
            short[] mono16 = toMono16(src, sampleRate, channels, bitDepth);
            if (mono16 == null) {
                return pcmBase64;
            }
            short[] resampled = (sampleRate == 16000 ? mono16 : resampleTo16k(mono16, sampleRate));
            byte[] out = shortsToLittleEndianBytes(resampled);
            return java.util.Base64.getEncoder().encodeToString(out);
        } catch (Exception ignore) {
            return pcmBase64;
        }
    }

    // 将任意位深/通道的PCM转为单声道16bit（不更改采样率）
    private short[] toMono16(byte[] pcmBytes, int sampleRate, int channels, int bitDepth) {
        if (pcmBytes == null || sampleRate <= 0 || channels <= 0) return null;
        int bytesPerSample = bitDepth / 8;
        if (!(bitDepth == 8 || bitDepth == 16 || bitDepth == 24 || bitDepth == 32)) return null;
        if (pcmBytes.length % (bytesPerSample * channels) != 0) return null;
        int frameCount = pcmBytes.length / (bytesPerSample * channels);
        short[] mono = new short[frameCount];
        int idx = 0;
        for (int i = 0; i < frameCount; i++) {
            int accum = 0;
            for (int ch = 0; ch < channels; ch++) {
                int sample;
                int base = idx + ch * bytesPerSample;
                switch (bitDepth) {
                    case 8: {
                        int b = pcmBytes[base] & 0xFF; // unsigned 8-bit
                        sample = ((b - 128) << 8); // scale to 16-bit range
                        break;
                    }
                    case 16: {
                        int lo = pcmBytes[base] & 0xFF;
                        int hi = pcmBytes[base + 1]; // signed
                        sample = (short) ((hi << 8) | lo);
                        break;
                    }
                    case 24: {
                        int b0 = pcmBytes[base] & 0xFF;
                        int b1 = pcmBytes[base + 1] & 0xFF;
                        int b2 = pcmBytes[base + 2]; // signed
                        int v = (b2 << 16) | (b1 << 8) | b0; // sign-extended via b2
                        // downscale to 16-bit by discarding low 8 bits
                        sample = (short) (v >> 8);
                        break;
                    }
                    case 32: {
                        int b0 = pcmBytes[base] & 0xFF;
                        int b1 = pcmBytes[base + 1] & 0xFF;
                        int b2 = pcmBytes[base + 2] & 0xFF;
                        int b3 = pcmBytes[base + 3]; // signed
                        int v = (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
                        // downscale to 16-bit by discarding low 16 bits
                        sample = (short) (v >> 16);
                        break;
                    }
                    default:
                        sample = 0;
                }
                accum += sample;
            }
            int avg = accum / channels;
            if (avg > Short.MAX_VALUE) avg = Short.MAX_VALUE;
            if (avg < Short.MIN_VALUE) avg = Short.MIN_VALUE;
            mono[i] = (short) avg;
            idx += bytesPerSample * channels;
        }
        return mono;
    }

    // 简单线性插值重采样到16kHz
    private short[] resampleTo16k(short[] src, int srcSampleRate) {
        if (src == null || srcSampleRate <= 0) return src;
        if (srcSampleRate == 16000) return src;
        double ratio = 16000.0 / srcSampleRate;
        int dstLen = (int) Math.max(1, Math.round(src.length * ratio));
        short[] dst = new short[dstLen];
        for (int i = 0; i < dstLen; i++) {
            double srcPos = i / ratio;
            int i0 = (int) Math.floor(srcPos);
            int i1 = Math.min(i0 + 1, src.length - 1);
            double frac = srcPos - i0;
            double s0 = src[i0];
            double s1 = src[i1];
            int v = (int) Math.round(s0 + (s1 - s0) * frac);
            if (v > Short.MAX_VALUE) v = Short.MAX_VALUE;
            if (v < Short.MIN_VALUE) v = Short.MIN_VALUE;
            dst[i] = (short) v;
        }
        return dst;
    }

    private byte[] shortsToLittleEndianBytes(short[] data) {
        byte[] out = new byte[data.length * 2];
        int j = 0;
        for (short v : data) {
            out[j++] = (byte) (v & 0xFF);
            out[j++] = (byte) ((v >> 8) & 0xFF);
        }
        return out;
    }
}